import { useState, useEffect, useReducer, useRef, useCallback, useMemo } from 'react'
import './index.less'
import Header from './Header'
import ItemList from './ItemList'
import Modal from './Modal'

export default function TodoListWithHook() {
  // state.todoList增删改查
  const [state, dispatch] = useReducer((state, action) => {
    if (action.type === 'add') {
      const newTodoList = [...state.todoList, {
        id: new Date().getTime(),
        completed: false,
        title: action.val
      }]
      return { todoList: newTodoList }
    } else if (action.type === 'toggle') {
      const newTodoList = state.todoList.map(obj => {
        return (obj.id === action.id) ? { ...obj, completed: !obj.completed } : obj
      })
      return { todoList: newTodoList }
    } else if (action.type === 'edit') {
      const newTodoList = state.todoList.map(obj => {
        return (obj.id === action.id) ? { ...obj, title: action.val } : obj
      })
      return { todoList: newTodoList }
    } else if (action.type === 'del') {
      return { todoList: state.todoList.filter(obj => obj.id !== action.id) }
    } else {
      return false
    }
  }, [], () => {
    // 用这种方式惰性初始state 只在componentDidMouted触发一次JSON.parse
    console.log('触发JSON.parse')
    // useState也有惰性初始state 下面这样componentDidMount componentDidUpdate都会运行JSON.parse
    // const [todoList, setTodoList] = useState(JSON.parse(localStorage.getItem('reactHookTodoList') || '[]'))
    // 下面这样 惰性初始state 只在componentDidMount运行JSON.parse
    // const [todoList, setTodoList] = useState(() => JSON.parse(localStorage.getItem('reactHookTodoList') || '[]'))
    return { 
      todoList: JSON.parse(localStorage.getItem('reactHookTodoList') || '[]') 
    }
  })

  // 增
  const [isShowAddInput, setIsShowAddInput] = useState(false)
  // 子组件Header ItemList Modal都加了React.memo 那么组件对应的props变化才会重新执行子组件
  // 1 如果是 const showAddInput = () => {setIsShowAddInput(!isShowAddInput)}
  // 每次父组件刷新 showAddInput算新函数 导致Header组件props变化 重新执行子组件Header
  // 2 如果是 const showAddInput = useCallback(() => {setIsShowAddInput(!isShowAddInput)})
  // 每次父组件刷新 showAddInput算新函数 导致Header组件props变化 重新执行子组件Header
  // 3 如果是 const showAddInput = useCallback(() => {setIsShowAddInput(!isShowAddInput)}, [])
  // 每次父组件刷新 showAddInput都不算新函数 都不导致Header组件props变化 都不重新执行子组件Header
  // 4 如果是 const showAddInput = useCallback(() => {setIsShowAddInput(!isShowAddInput)}, [isShowAddInput])
  // 每次变化isShowAddInput导致父组件刷新 showAddInput才算新函数 才导致Header组件props变化 才重新执行子组件Header 
  // 其他任何的state变化导致父组件刷新 都不会重新执行子组件Header
  // 通过在各子组件props方法上包装useCallback 精确控制各子组件不必要的刷新
  const showAddInput = useCallback(() => {
    setIsShowAddInput(!isShowAddInput)
  }, [isShowAddInput])
  const addItem = useCallback((val) => {
    dispatch({ type: 'add', val })
    setIsShowAddInput(false)
  }, [state.todoList, isShowAddInput])

  // 查
  const [isShowModal, setIsShowModal] = useState(false)
  const [modalTitle, setModalTitle] = useState('')
  const [modalContent, setModalContent] = useState('')
  const closeModal = useCallback(() => {
    setIsShowModal(false)
  }, [isShowModal])
  const viewItem = useCallback((itemId) => {
    const { id, title, completed } = state.todoList.find(obj => obj.id === itemId) || {}
    setModalTitle('查看事件')
    setModalContent(<div className='innerContent'>
      <div>时间：{new Date(id).toLocaleString()}</div>
      <div>内容：{title}</div>
      <div>状态：{completed ? '完成' : '未完成'}</div>
      <button className='btn' onClick={() => setIsShowModal(false)}>确定</button>
    </div>)
    setIsShowModal(true)
  }, [state.todoList])

  // 改completed
  const toggleItem = useCallback((id) => {
    dispatch({ type: 'toggle', id })
  }, [state.todoList])

  // 改
  const textareaRef = useRef(null)
  const editItem = useCallback((itemId) => {
    const changeTodoTitle = () => {
      const val = textareaRef.current.value.trim()
      if (val) {
        dispatch({ type: 'edit', id: itemId, val })
        setIsShowModal(false)
      }
    }
    const { id, title } = state.todoList.find(obj => obj.id === itemId) || {}
    setModalTitle('编辑事件')
    // 这里必须要加key 否者defaultValue不会更新
    setModalContent(<div className='innerContent'>
      <textarea key={id} className='textarea' defaultValue={title} ref={textareaRef}></textarea>
      <button className='btn' onClick={changeTodoTitle}>确定</button>
    </div>)
    setIsShowModal(true)
  }, [state.todoList])

  // 删
  const delItem = useCallback((itemId) => {
    dispatch({ type: 'del', id: itemId })
  }, [state.todoList])

  // state.todoList的变化 存储到localStorage
  useEffect(() => {
    // 相当于componentDidMount componentDidUpdate
    console.log('useEffect 触发了')
    localStorage.setItem('reactHookTodoList', JSON.stringify(state.todoList))
    // 相当于前一次的componentDidUpdate componentWillUnmount
    return () => { console.log('useEffect 组件更新(props或者state变化)或者组件卸载了') }
    // 如果不传入第二个参数 那任何props或者state变化触发的componentDidUpdate都会触发useEffect
    // 如果第二个参数传入[] 那componentDidUpdate不会触发useEffect
    // 如果第二个参数传入[state.todoList] 那componentDidUpdate只在state.todoList变化的时候触发useEffect
  }, [state.todoList])

  // state.todoList的变化 触发useMemo 类似vue的computed
  const done = useMemo(() => {
    console.log('useMemo done 触发了')
    return state.todoList.filter(obj => obj.completed).length
    // 如果不传入第二个参数 那任何props或者state变化触发的componentDidUpdate都会触发useMemo
    // 如果第二个参数传入[] 那componentDidUpdate不会触发useMemo
    // 如果第二个参数传入[state.todoList] 那componentDidUpdate只在state.todoList变化的时候触发useMemo
  }, [state.todoList])
  const total = useMemo(() => {
    console.log('useMemo total 触发了')
    return state.todoList.length
  }, [state.todoList])

  return (
    <div className="todoListWithHook">
      <div className="wrap">
        <Header
          show={isShowAddInput}
          showAdd={showAddInput}
          add={addItem}
          done={done}
          total={total}
        >
        </Header>
        <ItemList
          todoList={state.todoList}
          toggleItem={toggleItem}
          clickView={viewItem}
          clickEdit={editItem}
          clickDel={delItem}
        >
        </ItemList>
        <Modal
          isShowModal={isShowModal}
          closeModal={closeModal}
          title={modalTitle}
          content={modalContent}
        >
        </Modal>
      </div>
    </div>
  )
}
